package org.aspire.graphics.display;

import java.awt.Canvas;
import java.awt.DisplayMode;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.awt.image.VolatileImage;
import java.util.ArrayList;

import javax.swing.JFrame;

import org.aspire.core.config.PCConfig;
import org.aspire.core.sys.GSystem;
import org.aspire.data.Owner;
import org.aspire.graphics.Color;
import org.aspire.graphics.DisplayView;
import org.aspire.graphics.DrawGraphics;
import org.aspire.graphics.NativeGraphicsInfo;
import org.aspire.graphics.draw.JavaStandardGraphics;
import org.aspire.math.geom.Dimension;

/******************************************************************************
 * A display view implementation for the Java Standard Edition.
 * <p>
 * This display view uses either a buffer strategy or volatile image for 
 * creating the draw graphics to be used by the renderer.
 * 
 * @author Golden T Studios (GTGE)
 * @author JPACarabuena
 *****************************************************************************/
public class JavaStandardDisplay implements DisplayView, 
		NativeGraphicsInfo {

	// ========================================================================
	// Constants
	// ========================================================================

	// ========================================================================
	// Static Methods
	// ========================================================================

	// ========================================================================
	// Fields
	// ========================================================================
	
	/**************************************************************************
	 * The screen size of the display view.
	 *************************************************************************/
	protected Dimension displaySize;
	
	/**************************************************************************
	 * The owner of this display view.
	 *************************************************************************/
	protected Owner owner;

	/**************************************************************************
	 * The frame surface where graphics will be drawn.
	 *************************************************************************/
	protected JFrame frame;
	
	/**************************************************************************
	 * The canvas where graphics will be drawn.
	 *************************************************************************/
	protected Canvas canvas;
	
	/**************************************************************************
	 * The buffer strategy to create back buffers.
	 *************************************************************************/
	protected BufferStrategy strategy;
	
	/**************************************************************************
	 * The off-screen volatile image for creating back buffers. 
	 *************************************************************************/
	protected VolatileImage offscreen;
	
	/**************************************************************************
	 * The current draw graphics for this display view.
	 *************************************************************************/
	protected JavaStandardGraphics currentGraphics;
	
	/**************************************************************************
	 * The window listener for this display view.
	 *************************************************************************/
	protected PCWindowListener listener;

	/**************************************************************************
	 * Whether or not the display view is set to full screen exclusive mode.
	 *************************************************************************/
	protected boolean fullscreen;
	
	// ========================================================================
	// Constructors
	// ========================================================================

	/**************************************************************************
	 * Create a new standard java display view with the specified game
	 * configuration.
	 * 
	 * @param owner		The owner of this display view.
	 * @param config 	The game configuration.
	 *************************************************************************/
	public JavaStandardDisplay(Owner owner, PCConfig config) {
		this.owner = owner;
		this.displaySize = config.getScreenDisplaySize();

		this.listener = new PCWindowListener();
		
		this.fullscreen = config.isFullScreenExclusiveModeEnabled();
		
		this.currentGraphics = new JavaStandardGraphics();
		
		/* Create the Display Frame */
		
		this.initFrame(config);
		
		if(this.fullscreen) {
			try {
				this.initFullScreenExclusiveMode();
			} catch(RuntimeException e) {
				this.initFrame(config);
				this.initWindowedMode();
			}
		} else {
			this.initWindowedMode();
		}

		/* Create the BackBuffer */
		
		if(config.isBufferStrategyEnabled()) {
			this.createBufferStrategy();
		} 
		
		if(this.strategy == null) {
			this.createOffScreenBuffer();
		}
		
		if(this.fullscreen) {
			this.frame.requestFocus();
		} else {
			this.canvas.requestFocus();
		}
		
	}
	
	// ========================================================================
	// Getters & Setters
	// ========================================================================

	// ========================================================================
	// Override Methods
	// ========================================================================

	@Override
	public int getWidth() {
		return this.displaySize.getWidthInt();
	}

	@Override
	public int getHeight() {
		return this.displaySize.getHeightInt();
	}

	@Override
	public void setWidth(int width) {
		// do nothing
	}

	@Override
	public void setHeight(int height) {
		// do nothing	
	}
	
	@Override
	public Owner getOwner() {
		return this.owner;
	}

	@Override
	public DrawGraphics getBackBuffer() {
		if(this.currentGraphics.isEmpty()) {
			if(this.strategy == null) {
				if(this.offscreen.validate(NativeGraphicsInfo.CONFIG) 
						== VolatileImage.IMAGE_INCOMPATIBLE) {
					this.createOffScreenBuffer();
				}
				this.currentGraphics.setGraphics2D(
						this.offscreen.createGraphics());
			} else {
				this.currentGraphics.setGraphics2D((Graphics2D) this.strategy.
						getDrawGraphics());
			}
		}
		
		/* Clear Color */
		this.currentGraphics.setColor(Color.BLACK);
		this.currentGraphics.fillRect(0, 0, this.getWidth(), this.getHeight());
		
		return this.currentGraphics;
	}

	@Override
	public boolean flip() {
		this.currentGraphics.clear();
		
		if(this.strategy == null) {
			if(this.fullscreen) {
				this.frame.getGraphics().drawImage(
						this.offscreen, 0, 0, null);
			} else {
				this.canvas.getGraphics().drawImage(
						this.offscreen, 0, 0, null);
			}
			Toolkit.getDefaultToolkit().sync();
			return (!this.offscreen.contentsLost());
		} else {
			this.strategy.show();
			Toolkit.getDefaultToolkit().sync();
			return (!this.strategy.contentsLost());
		}
	}

	@Override
	public void cleanup() {
		try {
			Thread.sleep(200);
		} catch(InterruptedException e) {
			
		}
		
		try {
			if(this.frame != null) {
				this.frame.dispose();
			}
		} catch(Exception e) {
			
		}
	}

	// ========================================================================
	// Methods
	// ========================================================================

	/**************************************************************************
	 * Initialize the game display frame.
	 * 
	 * @param config	the game configuration.
	 *************************************************************************/
	public void initFrame(PCConfig config) {
		if(this.frame != null) {
			this.frame.dispose();
			this.frame = null;
		}
		
		this.frame = new JFrame(config.getGameTitle(),
				NativeGraphicsInfo.CONFIG);

		this.frame.addWindowListener(this.listener);
		this.frame.setResizable(false);
		this.frame.setIgnoreRepaint(true);
	}
	
	/**************************************************************************
	 * Initialize frame in full screen exclusive mode.
	 *************************************************************************/
	public void initFullScreenExclusiveMode() throws RuntimeException {
		if(!NativeGraphicsInfo.DEVICE.isFullScreenSupported()) {
			throw new RuntimeException("FullScreen Exclusive Mode Not " +
					"Supported");
		}
		
		this.frame.setLayout(null);
		this.frame.setUndecorated(true);
		this.frame.dispose();
		
		NativeGraphicsInfo.DEVICE.setFullScreenWindow(this.frame);
		
		if(!NativeGraphicsInfo.DEVICE.isDisplayChangeSupported()) {
			NativeGraphicsInfo.DEVICE.setFullScreenWindow(null);
			this.frame.dispose();
			throw new RuntimeException("Display Change Not Supported");
		}
		
		DisplayMode bestMode = this.getBestDisplayMode(this.displaySize);
		if(bestMode == null) {
			NativeGraphicsInfo.DEVICE.setFullScreenWindow(null);
			this.frame.dispose();
			throw new RuntimeException("Changing Display Mode to " +
					this.displaySize.getWidthInt() + "x" +
					this.displaySize.getHeightInt() + " Not Supported");
		}
		
		NativeGraphicsInfo.DEVICE.setDisplayMode(bestMode);
		
		try {
			Thread.sleep(1000L);
		} catch(InterruptedException e) {
			
		}
	}
	
	/**************************************************************************
	 * Initialize frame in windowed mode.
	 *************************************************************************/
	public void initWindowedMode() {
		/* Initialize Canvas */
		this.canvas = new Canvas(NativeGraphicsInfo.CONFIG);
		this.canvas.setIgnoreRepaint(true);
		this.canvas.setSize(this.getWidth(), this.getHeight());
		
		/* Resize Frame to Fit Canvas */
		this.frame.setVisible(true);
		Insets insets = this.frame.getInsets();
		this.frame.setVisible(false);
		this.frame.setSize(this.getWidth() + insets.left + insets.right,
				this.getHeight() + insets.top + insets.bottom);
		this.frame.add(this.canvas);
		this.frame.pack();
		this.frame.setLayout(null);
		this.frame.setLocationRelativeTo(null);
		
		/* Relocate Frame */
		if(this.frame.getX() < 0) {
			this.frame.setLocation(0, this.frame.getY());
		}
		if(this.frame.getY() < 0) {
			this.frame.setLocation(this.frame.getX(), 0);
		}
		this.frame.setVisible(true);
	}
	
	/**************************************************************************
	 * Creates a double buffer strategy.
	 *************************************************************************/
	public void createBufferStrategy() {
		boolean bufferCreated;
		int num = 0;

		do {
			bufferCreated = true;
			try {
				if(this.fullscreen) {
					this.frame.createBufferStrategy(2);
				} else {
					this.canvas.createBufferStrategy(2);
				}
			} catch(Exception e) {
				bufferCreated = false;
				try {
					Thread.sleep(200);
				} catch(InterruptedException exc) {
					
				}
			}
			
			if(num++ > 5) {
				break;
			}
		} while(!bufferCreated);
		
		if(!bufferCreated) {
			return;
		}
		
		while(this.strategy == null) {
			try {
				this.strategy = this.fullscreen ? 
						this.frame.getBufferStrategy() :
							this.canvas.getBufferStrategy();
			} catch(Exception e) {
				
			}
		}

		GSystem.println("?");
				
		DrawGraphics gfx = null;
		while(gfx == null) {
			try {
				gfx = this.getBackBuffer();
			} catch(Exception e) {
				
			}
		}
	}
	
	/**************************************************************************
	 * Creates the off screen volatile image.
	 *************************************************************************/
	public void createOffScreenBuffer() {
		if(this.offscreen != null) {
			this.offscreen.flush();
			this.offscreen = null;
		}
		
		this.offscreen = NativeGraphicsInfo.CONFIG.
				createCompatibleVolatileImage(this.getWidth(), 
						this.getHeight());
	}
	
	/**************************************************************************
	 * Gets the best display mode given the specified size.
	 * 
	 * @param size	the screen size of the display view.
	 * @return		the best display mode.
	 *************************************************************************/
	public DisplayMode getBestDisplayMode(Dimension size) {
		DisplayMode modes[] = NativeGraphicsInfo.DEVICE.getDisplayModes();
		
		/* Looks for DisplayMode with the same dimension as the game */
		ArrayList<DisplayMode> matchModes = new ArrayList<DisplayMode>();
		for(int i=0; i<modes.length; i++) {
			if(modes[i].getWidth() == this.getWidth() 
					&& modes[i].getHeight() == this.getHeight()) {
				matchModes.add(modes[i]);
			}
		}
		
		/* The Best DisplayMode is usually the last entry of the Array */
		DisplayMode matchModesArray[] = matchModes.toArray(new DisplayMode[0]);
		DisplayMode best = matchModesArray.length==0 ? 
				null : matchModesArray[matchModesArray.length-1];
		
		return best;
	}
	
	// ========================================================================
	// Inner & Anonymous Classes
	// ========================================================================
}